Ana içeriğe geç

Traits

Traitler yani türkçe çevirisi ile kişisel özellikler bir tipin yapabildiği ve diğer tiplere paylaşabildiği özellikleri ifade etmektedir. Traitleri ortaklaşılmış özellikleri soyut olarak kullanmak için tanımlayabiliriz. Hadi başlayalım.

İlk yapmamız gereken trait bloğunu trait anahtar kelimesi ve istediğimiz isimle oluştururuz.

trait Gozlemle{

}

Oluşturduğumuz bu trait içerisine istediğimiz methodun neler alacağını ve ne çıktı vereceğini tanıtmamız gerekmektedir.

trait Overview{
fn Overview(&self) -> String;
}

Yukarıda ifade edilidiği üzere fonksiyonun girdisini ve çıktısını tanımladık. Şimdi hadi bu fonksiyonu ortak olarak kapsayacak struct yapılarını oluşturalım:

struct Course {
headline: String,
author: String
}
struct AnotherCourse {
headline: String,
author: String
}

İki adet struct yapısını oluşturduk. Artık bu yapıların içerisine trait yapımızı impl ile implement edebiliriz.

impl Overview for Course{
fn overview(&self) -> String {
format!("{} {}", self.author, self.headline)
}
}
impl Overview for AnotherCourse{
fn overview(&self) -> String {
format!("{} {}", self.author, self.headline)
}
}

Yukarıda da görüntüleyebileceğiniz gibi overview traits yapısını iki struct için de ekledik.

Farklı bir özellik olarak bu traits yapıları içerisine defult değerler de tanımlamak mümkündür.

trait Overview{
fn Overview(&self) -> String {
String::from("Bu bir Rust Kursudur!")
}
}

Sonrasında bu kurları değişken olarak oluşturabiliriz.

let course1 = Course(headline: String::from("Hedline", author: String::from("Aybars")))
let course2 = AnotherCourse(headline: String::from("Another Hedline", author: String::from("İrem ")))

Sonrasında bu yazdıklarımız içerisindeki fonksiyonu kullanabiliriz:

println!("{}", course1.overview());
println!("{}", course2.overview());

Çalıştırdığımızda alt kısımdaki bir çıktı çıkacak ve bu kısımda defult olarak eklediğimiz çıktının olmadığını görüntüleyebilirsiniz.

Aybars, headline,
İrem, another hadline

Bu durumun nedeni bizim aslında impl içerisinde overwrite yapıyor olmamızdır. Eğer bu yapıların içerisini temizlersek:

impl Overview for Course{}
impl Overview for AnotherCourse{}

Şu şekilde bir sonuç ile karşılaşırız:

Bu bir Rust Kursudur!
Bu bir Rust Kursudur!

## Trait Değerlerini Parametre Olarak Girmek

Trait değerlerini birer parametre olarak da kullanmamız mümkündür. İlk olarak bir foksiyon oluşturarak bu işleme başlayabiliriz.

fn call_overview(item: &impl Overview){
println!("Overview: {}", item.overview())
}

Sonrasında bu fonksiyou çağırabiliriz.

call_overview(&course1);
call_overview(&course2);

Aynı zamanda generics kullanarak fonksiyonu biraz daha basitleştirebiliriz.

fn call_overview<T: Overview>(item: &T){
println!("Overview: {}", item.overview())
}

Şimdi birazcık geriye doğru açılalım ve yaptıklarımızı düşünelim. Traitler aslında veri tiplerine fonksiyon eklememizi sağlayan bir yapı. E diyebilirsiniz ki bunu zaten struct methodları yapmıyor mu? Yapıyor ancak tek bir yapı türüne yapıyor. Traitler ile oluşturduğumuz bu özelliği birden fazla türe aktarabiliriz. Şöyle düşünelim. Bizim bir fabrikamız var ve bu fabrikada araba, uçak, motorsiklet üretiyorsunuz. Bunların hepsinin modelleri var. Ama aynı zamanda her birinin bir ortak özelliği de var. Her bir araba modeli için yeniden tekerlek kodu yazmaktansa bir tekerlek kodunu trait olarak tanımlayarsak sonrasına bu trait değerini tüm araba structlarımıza verebiliriz.

Şimdi biraz daha iyi anladıysak bu iki fonksiyonun birbirinden farkına bakalım. İlk yazdığımız fonksiyon türünde her bir item için istediğimiz türü kullanabilecekken, Generics ile yazdığımız ikinci versiyonda ise hangi türü verirsen ver bu tür tüm diğer itemler için ortak olmalıdır.

fn overview(item1: &impl Overview, item2: &impl Overview) // item1 ve item2 nin tipi aynı olmak zorunda değil

fn overview<T: Overview>(item1: &T, item2: &T) // item1 ve item2 nin tipi aynı olmak zorunda

Aynı zamanda birden çok trait de kullanabiliriz. Bunu da inceleyebilirsiniz.

Utility Traitleri

Bu dersimizde utility traits yani türkçe çevirisi ile faydalı özellikler'i öğreneceğiz. Bu özellikler standart olarak Rust içerisinde bulunan ve Rust kodlarının yazımında büyük önem arz eden bu nedenle de öğrenmemiz gereken yapılardır.

1. Drop Trait

Aslında kursun içerisinde bu kelimenin çokça kullanıldığını duymuş olabiliriz. Rust içerisinde bu kavarmı konuşunca da bir değişkeni değerinden kurtarmak olarak düşünürüz.

Bu drop işlemleri birçok yerde gerçekleşir. Bir değişkenin scope dışı olması veya bitmesi bu duruma örnek olarak verilebilir. Genelde Rust bunu bizim için otamatik yapsa da bizde nasıl kullanacağımızı bilmek isteyebiliriz.

Bu yapıyı oluşturmak için struct yapısı içeisine bir method şeklinde ekleme yapabiliriz.

impl Drop for Course{
fn drop(&mut self){
println!("Dropping: {}")
}
}

Oluşturduğumuz bu drop fonksiyonu özel bir fonksiyondur. Direkt olarak çağırabilir.

drop(course1)

Fonksiyonumuzu çalıştırdığımuzda ekrana yazdırıldığını görebiliriz. Ancak bu yapıyı hiç kullanmasak da aynı şekilde bir çıktıyı alacağız. Bu durumun nedeni course1 değerinin kod bloğu dışında drop olmasından dolayı aynı çıktının oluştuğunu görüntüleyebiliriz.

2. Clone Trait

Clone Trait kendisinin kopyasını oluşturabilen tipler için olmaktadır. Bu trait içerisinde çalışacak kodların oluşabilmesi için self ibaresine girilen değerin harici bir clone'unun oluşması gerekmektedir.

3. Copy Trait

Daha önce de copy yapısını konuşmuş olsak da şimdi biraz daha ayrıntılı bakabiliriz. i32 gibi basit ve herhangi bir kaynak gerektirmeyen değerler basit bir şekilde kopyalanabilir. Bu demek olmaktadır ki içerisinde kendinden farklı bir kaynağı barındıran hiçbir tip copy type yani kopyalanabilir tür olamaz.

From / into traitleri ve Operating Overloading de incelenebilir.